function WebGLAttributes(gl, capabilities) {
  const isWebGL2 = capabilities.isWebGL2;

  const buffers = new WeakMap();

  function createBuffer(attribute, bufferType) {
    const array = attribute.array;
    const usage = attribute.usage;

    const buffer = gl.createBuffer();

    gl.bindBuffer(bufferType, buffer);
    gl.bufferData(bufferType, array, usage);

    attribute.onUploadCallback();

    let type = gl.FLOAT;

    if (array instanceof Float32Array) {
      type = gl.FLOAT;
    } else if (array instanceof Float64Array) {
      console.warn('THREE.WebGLAttributes: Unsupported data buffer format: Float64Array.');
    } else if (array instanceof Uint16Array) {
      if (attribute.isFloat16BufferAttribute) {
        if (isWebGL2) {
          type = gl.HALF_FLOAT;
        } else {
          console.warn('THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.');
        }
      } else {
        type = gl.UNSIGNED_SHORT;
      }
    } else if (array instanceof Int16Array) {
      type = gl.SHORT;
    } else if (array instanceof Uint32Array) {
      type = gl.UNSIGNED_INT;
    } else if (array instanceof Int32Array) {
      type = gl.INT;
    } else if (array instanceof Int8Array) {
      type = gl.BYTE;
    } else if (array instanceof Uint8Array) {
      type = gl.UNSIGNED_BYTE;
    }

    return {
      buffer,
      type,
      bytesPerElement: array.BYTES_PER_ELEMENT,
      version: attribute.version,
    };
  }

  function updateBuffer(buffer, attribute, bufferType) {
    const array = attribute.array;
    const updateRange = attribute.updateRange;

    gl.bindBuffer(bufferType, buffer);

    if (updateRange.count === -1) {
      // Not using update ranges

      gl.bufferSubData(bufferType, 0, array);
    } else {
      if (isWebGL2) {
        gl.bufferSubData(
          bufferType,
          updateRange.offset * array.BYTES_PER_ELEMENT,
          array,
          updateRange.offset,
          updateRange.count,
        );
      } else {
        gl.bufferSubData(
          bufferType,
          updateRange.offset * array.BYTES_PER_ELEMENT,
          array.subarray(updateRange.offset, updateRange.offset + updateRange.count),
        );
      }

      updateRange.count = -1; // reset range
    }
  }

  //

  function get(attribute) {
    if (attribute.isInterleavedBufferAttribute) attribute = attribute.data;

    return buffers.get(attribute);
  }

  function remove(attribute) {
    if (attribute.isInterleavedBufferAttribute) attribute = attribute.data;

    const data = buffers.get(attribute);

    if (data) {
      gl.deleteBuffer(data.buffer);

      buffers.delete(attribute);
    }
  }

  function update(attribute, bufferType) {
    if (attribute.isGLBufferAttribute) {
      const cached = buffers.get(attribute);

      if (!cached || cached.version < attribute.version) {
        buffers.set(attribute, {
          buffer: attribute.buffer,
          type: attribute.type,
          bytesPerElement: attribute.elementSize,
          version: attribute.version,
        });
      }

      return;
    }

    if (attribute.isInterleavedBufferAttribute) attribute = attribute.data;

    const data = buffers.get(attribute);

    if (data === undefined) {
      buffers.set(attribute, createBuffer(attribute, bufferType));
    } else if (data.version < attribute.version) {
      updateBuffer(data.buffer, attribute, bufferType);

      data.version = attribute.version;
    }
  }

  return {
    get,
    remove,
    update,
  };
}

export { WebGLAttributes };
